#include "Emulator.h"

const struct option Emulator::long_options[] = {
  { "seed",           1, NULL, 's' },
  { "max-cycles",     1, NULL, 'C' },
  { "image",          1, NULL, 'i' },
  { "log-begin",      1, NULL, 'b' },
  { "log-end",        1, NULL, 'e' },
  { "verbose",        1, NULL, 'v' },
  { "help",           0, NULL, 'h' },
  { 0,                0, NULL,  0  }
};

void Emulator::print_help(const char *file) {
  printf("Usage: %s [OPTION...]\n", file);
  printf("\n");
  printf("  -s, --seed=NUM        use this seed\n");
  printf("  -C, --max-cycles=NUM  execute at most NUM cycles\n");
  printf("  -i, --image=FILE      run with this image file\n");
  printf("  -b, --log-begin=NUM   display log from NUM th cycle\n");
  printf("  -e, --log-end=NUM     stop display log at NUM th cycle\n");
  printf("  -v, --verbose=STR     verbosity level, can be one of [ALL, DEBUG, INFO, WARN, ERROR]\n");
  printf("  -h, --help            print program help info\n");
  printf("\n");
}
vector<const char *> Emulator::parse_args(int argc, const char *argv[]) {
  vector<const char *> args = { argv[0] };
  int o;
  extern uint64_t getLogLevel(const char * str);
  while ( (o = getopt_long(argc, const_cast<char *const*>(argv), "-s:C:hi:m:b:e:v:", long_options, NULL)) != -1) {
    switch (o) {
      case 's': 
        if(std::string(optarg) != "NO_SEED") {
          seed = atoll(optarg);
          printf("Using seed = %d\n", seed);
        }
        break;
      case 'C': max_cycles = atoll(optarg);  break;
      case 'i': image = optarg;
                args.push_back("-i");
                args.push_back(optarg);
                break;
      case 'b': log_begin = atoll(optarg);  break;
      case 'e': log_end = atoll(optarg); break;
      case 'v': log_level = getLogLevel(optarg); break;
      default:
                print_help(argv[0]);
                exit(0);
    }
  }
//#include <getopt.h>
  return args;
}

Emulator::Emulator(int argc, const char *argv[]):
    top(new VMySoCSim),
    image(nullptr),
    seed(0),max_cycles(-1),cycles(0),main_times(0),
    log_begin(0), log_end(-1), log_level(LOG_ALL)
{
    auto args = parse_args(argc, argv);

    srand(seed);

    // init ram
    extern void init_ram(const char *img);
    init_ram(image);
}

Emulator::~Emulator()
{
    delete top;
}

void Emulator::read_emu_regs(rtlreg_t *r)
{
#define macro(x) r[x] = this->top->io_difftest_r_##x
macro(0); macro(1); macro(2); macro(3); macro(4); macro(5); macro(6); macro(7);
    macro(8); macro(9); macro(10); macro(11); macro(12); macro(13); macro(14); macro(15);
    macro(16); macro(17); macro(18); macro(19); macro(20); macro(21); macro(22); macro(23);
    macro(24); macro(25); macro(26); macro(27); macro(28); macro(29); macro(30); macro(31);
    r[DIFFTEST_THIS_PC] = top->io_difftest_thisPC; r[DIFFTEST_MSTATUS] = top->io_difftest_mstatus;
    r[DIFFTEST_MCAUSE] = top->io_difftest_mcause; r[DIFFTEST_MEPC] = top->io_difftest_mepc;
}

void Emulator::single_cycle(){
    top->clock = 0;
    top->eval();
#ifdef VCD
    //if(this->cycles > 6900000)
     tfp->dump(main_times++);
#endif
    top->clock = 1;
    top->eval();
#ifdef VCD
    //if(this->cycles > 6900000)
       tfp->dump(main_times++);
#endif
    this->cycles++;
}

void Emulator::execute_cycles(uint64_t n){
    extern bool is_finish();
    extern void set_abort();
    int hascommit = 0;
    uint32_t lasttime = 0;
    uint64_t lastcommit = n;

    printf(FONT_COLOR_GREEN"\t==================== MYCPU EMULATION START ====================\t\n"COLOR_NONE);
#ifdef VCD
#define WAVE_SWITCH "ON"
#else
#define WAVE_SWITCH "OFF"
#endif
    printf("wave: " WAVE_SWITCH "\n") ;
    // reset for a few cycles to support pipelined reset
    reset_n(10);
    while (!is_finish() && n > 0)
    {
        /* code */
        single_cycle();
        n--;
#define __DIFFTEST__

        if(!hascommit && (top)->io_difftest_thisPC == 0x80000000) {
            hascommit = 1;

            // init difftest
            extern void init_difftest(rtlreg_t *reg);
            rtlreg_t reg[DIFFTEST_MEPC + 1];
            read_emu_regs(reg);
            init_difftest(reg);

        }
        //difftest
        if(top->io_difftest_commit && hascommit) {
#ifdef __DIFFTEST__
            rtlreg_t reg[DIFFTEST_MEPC + 1];
            read_emu_regs(reg);
            extern int difftest_step(rtlreg_t *reg_scala, uint32_t this_inst, int isMMIO, int isRVC, uint64_t intrNO);
            if(top->io_difftestCtrl_enable){
                if(difftest_step(reg, top->io_difftest_thisINST, top->io_difftest_isMMIO,
                    top->io_difftest_isRVC,top->io_difftest_intrNO)) {
                        for (size_t i = 0; i < 20; i++){    single_cycle(); }
                        //printf("cycles = %ld\n", this->cycles);
#ifdef VCD
                    tfp->close();
#endif
                    set_abort();
                }
            }
#endif
            extern void haltCheck(uint64_t pc, uint64_t inst, uint64_t a0);
            haltCheck(top->io_difftest_thisPC, top->io_difftest_thisINST, top->io_difftest_r_10);
        }


    }
}

void Emulator::execute() {

#ifdef VCD
    Verilated::traceEverOn(true);	// Verilator must compute traced signals
    VL_PRINTF("Enabling waves...\n");
    tfp = new VerilatedVcdC;
    top->trace(tfp, 99);	// Trace 99 levels of hierarchy
    tfp->open("vlt_dump.vcd");	// Open the dump file
#endif
    
    execute_cycles(max_cycles);

}
